package pong;

import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.List;

import processing.core.PApplet;

public class PongGame extends PApplet {

	private static final long serialVersionUID = 1L;
	private List<Drawable> componentsToDraw;
	private int windowWidth, windowHeight;
	private PongBat player1, player2;
	private PongBall ball;
	private boolean isUpKeyPressed = false, isDownKeyPressed = false,
			isWKeyPressed = false, isSKeyPressed = false;

	private int player1Score = 0, player2Score = 0;

	@Override
	public void draw() {
		background(0);
		rectMode(CENTER);

		drawScores();
		drawMiddleline(9);
		fill(255);
		for (final Drawable drawable : componentsToDraw) {
			drawable.draw(this);
		}
		stroke(120);
		update(1000 / frameRate);

		checkKeys();

		text(frameRate, 50, windowHeight - 50);
	}

	@Override
	public void setup() {
		super.setup();
		frameRate(30);
		smooth();

		windowWidth = 800;
		windowHeight = 500;

		size(windowWidth, windowHeight);

		componentsToDraw = new ArrayList<Drawable>();

		final Rectangle player1Scope = new Rectangle(0, 0, windowWidth / 2,
				windowHeight);
		final Rectangle player2Scope = new Rectangle(windowWidth / 2, 0,
				windowWidth / 2, windowHeight);

		player1 = new PongBat(new Vector2(100, height / 2), player1Scope, 50,
				10);
		player2 = new PongBat(new Vector2(windowWidth - 100, height / 2),
				player2Scope, 50, 10);

		ball = new PongBall(new Vector2(windowWidth / 2, windowHeight / 2),
				new Vector2(1, 1), 200);

		componentsToDraw.add(player1);
		componentsToDraw.add(player2);
		componentsToDraw.add(ball);
	}

	@Override
	public void keyPressed(KeyEvent ke) {
		if (ke.getKeyCode() == KeyEvent.VK_UP) {
			this.isUpKeyPressed = true;
		} else if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
			this.isDownKeyPressed = true;
		} else if (ke.getKeyCode() == KeyEvent.VK_W) {
			this.isWKeyPressed = true;
		} else if (ke.getKeyCode() == KeyEvent.VK_S) {
			this.isSKeyPressed = true;
		} else if (ke.getKeyCode() == KeyEvent.VK_Y) {
			this.frameRate(--frameRate);
		} else if (ke.getKeyCode() == KeyEvent.VK_X) {
			this.frameRate(++frameRate);
		}
	}

	@Override
	public void keyReleased(KeyEvent ke) {
		if (ke.getKeyCode() == KeyEvent.VK_UP) {
			this.isUpKeyPressed = false;
		} else if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
			this.isDownKeyPressed = false;
		} else if (ke.getKeyCode() == KeyEvent.VK_W) {
			this.isWKeyPressed = false;
		} else if (ke.getKeyCode() == KeyEvent.VK_S) {
			this.isSKeyPressed = false;
		}
	}

	private void checkKeys() {
		if (isUpKeyPressed) {
			player2.move(0, -500 / frameRate);
		}
		if (isDownKeyPressed) {
			player2.move(0, 500 / frameRate);
		}
		if (isWKeyPressed) {
			player1.move(0, -500 / frameRate);
		}
		if (isSKeyPressed) {
			player1.move(0, 500 / frameRate);
		}
	}

	private void update(float dT) {
		while (dT > 0) {
			float simTime = simulationTime(dT);
			simulate(simTime);
			dT -= simTime;
		}
	}

	private float simulationTime(float dT) {
		dT = min(dT, collisionTimePaddleLeft(dT));
		dT = min(dT, collisionTimePaddleRight(dT));
		dT = min(dT, collisionTimeBorderTop(dT));
		dT = min(dT, collisionTimeBorderBottom(dT));
		return dT;
	}

	private float collisionTimePaddleLeft(float dT) {
		if (ball.getDirection().getX() > 0) {
			return Float.MAX_VALUE;
		}
		// 2 * um hier lieber zu weit zu checken
		final Vector2 newPos = ball.position.add(ball.getDirection().mult(
				2 * ball.getSpeed() * dT / 1000));
		final Line2D line = new Line2D.Float(ball.position.getX(),
				ball.position.getY(), newPos.getX(), newPos.getY());
		// Grober Check, ob überhaupt
		if (player1.getBoundingBox().intersectsLine(line)) {
			// wir wissen, dass sie kollidieren werden, deswegen reicht ab hier,
			// nur noch mit dem y-Wert zu rechnen
			final float xPos = ball.getPosition().getX();
			final float xDir = ball.getDirection().getX();
			final float xDelta = ball.getWidth() / 2;
			float faktor = -1f;

			// Treffpunkt von vorne berechnen
			final float player1X = player1.position.getX() + player1.getWidth()
					/ 2;
			faktor = (player1X - xPos) / xDir;
			final float hitY = ball.position.getY() + faktor
					* ball.getDirection().getY();

			if (hitY < player1.position.getY() - player1.getHeight() / 2) {
				// Treffer von oben

				final float yPos = ball.getPosition().getY();
				final float yDir = ball.getDirection().getY();
				final float yDelta = ball.getWidth() / 2 - 1;
				float player1Y = player1.position.getY();

				player1Y = player1Y - (player1.getHeight() / 2);
				faktor = (player1Y + yDelta - yPos) / yDir;
				return (faktor / ball.getSpeed()) * 1000f;

			} else if (hitY > player1.position.getY() + player1.getHeight() / 2) {
				// Treffer von unten

				final float yPos = ball.getPosition().getY();
				final float yDir = ball.getDirection().getY();
				final float yDelta = ball.getWidth() / 2 - 1;
				float player1Y = player1.position.getY();
				player1Y = player1Y + (player1.getHeight() / 2);
				faktor = (player1Y - yDelta - yPos) / yDir;

				return (faktor / ball.getSpeed()) * 1000f;
			} else {
				// Treffer von vorne
				// xpos + faktor * xDir = player2x - xdelta
				faktor = (player1X - xPos + xDelta) / xDir;
				return (faktor / ball.getSpeed()) * 1000f;
			}
		}
		return Float.MAX_VALUE;
	}

	private float collisionTimePaddleRight(float dT) {
		if (ball.getDirection().getX() < 0)
			return Float.MAX_VALUE;
		final Vector2 newPos = ball.position.add(ball.getDirection().mult(
				2 * ball.getSpeed() * dT / 1000));

		final Line2D line = new Line2D.Float(ball.position.getX(),
				ball.position.getY(), newPos.getX(), newPos.getY());
		// Grober Check, ob überhaupt
		if (player2.getBoundingBox().intersectsLine(line)) {
			// wir wissen, dass sie kollidieren werden, deswegen reicht ab hier,
			// nur noch mit dem y-Wert zu rechnen
			final float xPos = ball.getPosition().getX();
			final float xDir = ball.getDirection().getX();
			final float xDelta = ball.getWidth() / 2;
			float faktor = -1f;

			// Treffpunkt von vorne berechnen
			final float player2X = player2.position.getX() - player2.getWidth()
					/ 2;
			faktor = (player2X - xPos) / xDir;
			final float hitY = ball.position.getY() + faktor
					* ball.getDirection().getY();

			if (hitY < player2.position.getY() - player2.getHeight() / 2) {
				// Treffer von oben

				final float yPos = ball.getPosition().getY();
				final float yDir = ball.getDirection().getY();
				final float yDelta = ball.getWidth() / 2 - 1;
				float player2Y = player2.position.getY();

				player2Y = player2Y - (player2.getHeight() / 2);
				faktor = (player2Y + yDelta - yPos) / yDir;
				return (faktor / ball.getSpeed()) * 1000f;

			} else if (hitY > player2.position.getY() + player2.getHeight() / 2) {
				// Treffer von unten

				final float yPos = ball.getPosition().getY();
				final float yDir = ball.getDirection().getY();
				final float yDelta = ball.getWidth() / 2 - 1;
				float player2Y = player2.position.getY();
				player2Y = player2Y + (player2.getHeight() / 2);
				faktor = (player2Y - yDelta - yPos) / yDir;

				return (faktor / ball.getSpeed()) * 1000f;
			} else {
				// Treffer von vorne
				// xpos + faktor * xDir = player2x - xdelta
				faktor = (player2X - xPos - xDelta) / xDir;
				return (faktor / ball.getSpeed()) * 1000f;
			}
		}
		return Float.MAX_VALUE;
	}

	private float collisionTimeBorderTop(float dT) {
		if (ball.getDirection().getY() > 0)
			return Float.MAX_VALUE;

		// 2 * um hier lieber zu weit zu checken
		final Vector2 newPos = ball.position.add(ball.getDirection().mult(
				2 * ball.getSpeed() * dT / 1000));

		// Grober Check, ob überhaupt
		if (newPos.getY() < 0) {
			final float yPos = ball.getPosition().getY();
			final float yDir = ball.getDirection().getY();
			final float yDelta = ball.getHeight() / 2;

			final float faktor = (yDelta - yPos) / yDir;
			return (faktor / ball.getSpeed()) * 1000f;

		}

		return Float.MAX_VALUE;
	}

	private float collisionTimeBorderBottom(float dT) {
		if (ball.getDirection().getY() < 0)
			return Float.MAX_VALUE;

		// 2 * um hier lieber zu weit zu checken
		final Vector2 newPos = ball.position.add(ball.getDirection().mult(
				2 * ball.getSpeed() * dT / 1000));

		// Grober Check, ob überhaupt
		if (newPos.getY() > height) {
			final float yPos = ball.getPosition().getY();
			final float yDir = ball.getDirection().getY();
			final float yDelta = ball.getHeight() / 2;

			final float faktor = (height - yDelta - yPos) / yDir;
			return (faktor / ball.getSpeed()) * 1000f;

		}

		return Float.MAX_VALUE;
	}

	private void drawMiddleline(final int lineCount) {
		final int lines = lineCount * 2 - 1;
		final int lineHeight = height / lines;
		for (int i = 0; i < lines; i++) {
			rect(width / 2, (i * 2 * lineHeight) + (lineHeight / 2),
					width / 70, lineHeight);
		}
	}

	private void drawScores() {
		text(player1Score + "", width / 4, height / 20);
		text(player2Score + "", width * 3 / 4, height / 20);
	}

	private boolean nearlyEquals(final float value1, final float value2) {
		return value1 <= value2 + 1f && value1 >= value2 - 1f;
	}

	private void simulate(float simTime) {
		ball.moveForTime(simTime);

		final float ballX = ball.position.getX();
		final float ballY = ball.position.getY();
		final float player1X = player1.position.getX();
		final float player1Y = player1.position.getY();
		final float player2X = player2.position.getX();
		final float player2Y = player2.position.getY();

		// Collision Reaction
		if (ballX <= (player1X + (player1.getWidth() / 2 + ball.getWidth() / 2))) {
			println("Kollision mit player1 kommt in Frage");
			// Kollision mit player1 kommt in Frage
			if (nearlyEquals(ballX - ball.getWidth() / 2,
					player1X + player1.getWidth() / 2)
					&& ballY >= player1Y - player1.getHeight() / 2
							- ball.getHeight() / 2
					&& ballY <= player1Y + player1.getHeight() / 2
							+ ball.getHeight() / 2) {
				// Kollision von vorne
				println("Kollision von vorne!");
				final Vector2 dir = ball.getDirection();
				dir.setX(Math.abs(dir.getX()));
				ball.setDirection(dir);
			} else if (ballX - ball.getWidth() / 2 < player1X
					+ player1.getWidth() / 2
					&& ballX + ball.getWidth() / 2 > player1X
							- player1.getWidth() / 2) {
				println("Kollision von oben/unten kommt in Frage!");
				final Vector2 dir = ball.getDirection();
				if (ballY + ball.getHeight() / 2 >= player1Y
						- player1.getHeight() / 2
						&& ballY < player1Y) {
					println("Kollision von oben!");
					dir.setY(-Math.abs(dir.getY()));
				} else if (ballY - ball.getHeight() / 2 <= player1Y
						+ player1.getHeight() / 2
						&& ballY > player1Y) {
					println("Kollision von unten!");
					dir.setY(Math.abs(dir.getY()));
				}
			}

		} else if (ballX >= (player2X - (player2.getWidth() / 2 + ball
				.getWidth() / 2))) {
			// Kollision mit player2 kommt in Frage
			if (nearlyEquals(ballX + ball.getWidth() / 2,
					player2X - player2.getWidth() / 2)
					&& ballY >= player2Y - player2.getHeight() / 2
							- ball.getHeight() / 2
					&& ballY <= player2Y + player2.getHeight() / 2
							+ ball.getHeight() / 2) {
				// Kollision von vorne
				final Vector2 dir = ball.getDirection();
				dir.setX(-Math.abs(dir.getX()));
				ball.setDirection(dir);
			} else if (ballX + ball.getWidth() / 2 > player2X
					- player2.getWidth() / 2
					&& ballX - ball.getWidth() / 2 < player2X
							+ player2.getWidth() / 2) {
				final Vector2 dir = ball.getDirection();
				if (ballY + ball.getHeight() / 2 >= player2Y
						- player2.getHeight() / 2
						&& ballY < player2Y) {
					println("Kollision von oben!");
					dir.setY(-Math.abs(dir.getY()));
				} else if (ballY - ball.getHeight() / 2 <= player2Y
						+ player2.getHeight() / 2
						&& ballY > player2Y) {
					println("Kollision von unten!");
					dir.setY(Math.abs(dir.getY()));
				}
			}
		}

		// Border Top Collision
		if (ballY <= ball.getHeight() / 2) {
			final Vector2 direction = ball.getDirection();
			direction.setY(Math.abs(direction.getY()));
			ball.setDirection(direction);
		} else if (ballY >= height - (ball.getHeight() / 2)) {
			final Vector2 direction = ball.getDirection();
			direction.setY(-Math.abs(direction.getY()));
			ball.setDirection(direction);
		}

		// Ball zurückholen
		if (ballX < 0) {
			player2Score++;
			ball.setPosition(new Vector2(width / 2, height / 2));
		} else if (ballX > width) {
			player1Score++;
			ball.setPosition(new Vector2(width / 2, height / 2));
		}

	}

	public static void main(String[] args) {
		PApplet.main(new String[] { "pong.PongGame" });
	}
}
